package com.postgresql.geotools.filter;

import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.Query;
import org.geotools.data.postgis.PostgisNGDataStoreFactory;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.swing.action.SafeAction;
import org.geotools.swing.data.JDataStoreWizard;
import org.geotools.swing.table.FeatureCollectionTableModel;
import org.geotools.swing.wizard.JWizard;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.Filter;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Map;

/**
 * Query Lab是在您自己的数据上使用表尝试过滤器和表达式的一个借口
 * 显示结果。
 * <p>
 * 记住，当你编程的时候，你有其他的选择，然后CQL解析器，你可以直接
 * *使用CommonFactoryFinder.getFilterFactory2()创建一个过滤器。
 */
@SuppressWarnings("serial")
public class QueryLab extends JFrame {

    private DataStore dataStore;
    private JComboBox<String> featureTypeCBox;
    private JTable table;
    private JTextField text;

    public static void main(String[] args) throws Exception {
        JFrame frame = new QueryLab();
        frame.setVisible(true);
    }


    /**
     * 接下来，我们将创建应用程序用户界面，其中包括用于输入查询的文本字段和用于显示查询选择的特性的数据的表。
     * 添加以下构造函数:
     */
    public QueryLab() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setLayout(new BorderLayout());

        text = new JTextField(80);
        // 包括选择一切!
        text.setText("include");
        getContentPane().add(text, BorderLayout.NORTH);

        table = new JTable();
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.setModel(new DefaultTableModel(5, 5));
        table.setPreferredScrollableViewportSize(new Dimension(500, 200));

        JScrollPane scrollPane = new JScrollPane(table);
        getContentPane().add(scrollPane, BorderLayout.CENTER);

        JMenuBar menubar = new JMenuBar();
        setJMenuBar(menubar);

        JMenu fileMenu = new JMenu("File");
        menubar.add(fileMenu);

        featureTypeCBox = new JComboBox<>();
        menubar.add(featureTypeCBox);

        JMenu dataMenu = new JMenu("Data");
        menubar.add(dataMenu);
        pack();
        /*
        * 接下来，我们添加菜单项和行动的文件菜单连接到一个shapefile或PostGIS数据库:
        * 每个操作都调用相同的方法，但是传入不同的数据存储工厂
        */
        fileMenu.add(
                new SafeAction("Open shapefile...") {
                    @Override
                    public void action(ActionEvent e) throws Throwable {
                        connect(new ShapefileDataStoreFactory());
                    }
                });
        fileMenu.add(
                new SafeAction("Connect to PostGIS database...") {
                    @Override
                    public void action(ActionEvent e) throws Throwable {
                        connect(new PostgisNGDataStoreFactory());
                    }
                });
        fileMenu.add(
                new SafeAction("Connect to DataStore...") {
                    @Override
                    public void action(ActionEvent e) throws Throwable {
                        connect(null);
                    }
                });
        fileMenu.addSeparator();
        fileMenu.add(
                new SafeAction("Exit") {
                    @Override
                    public void action(ActionEvent e) throws Throwable {
                        System.exit(0);
                    }
                });
        /*
        现在让我们看看数据菜单项和动作:
        */
        dataMenu.add(
                new SafeAction("Get features") {
                    @Override
                    public void action(ActionEvent e) throws Throwable {
                        filterFeatures();
                    }
                });
        dataMenu.add(
                new SafeAction("Count") {
                    @Override
                    public void action(ActionEvent e) throws Throwable {
                        countFeatures();
                    }
                });
        dataMenu.add(
                new SafeAction("Geometry") {
                    @Override
                    public void action(ActionEvent e) throws Throwable {
                        queryFeatures();
                    }
                });
    }

    /**
     * 使用更通用的DataStoreFinder，它获取连接参数的映射。
     *
     * 注意，可以使用相同的代码连接到DataStoreFactorySpi(服务提供者接口)参数指定的完全不同类型的数据存储。
     * 文件菜单操作使用ShapefileDataStoreFactory或PostgisNGDataStoreFactory的实例调用此方法。
     *
     * JDataStoreWizard显示一个对话框，其中的输入字段适合于shapefile或PostGIS数据库。
     * 它需要比JFileDataStoreChooser多几行代码，JFileDataStoreChooser用于提示用户输入一个shapefile，但允许更大的控制。
     *
     * 文件菜单操作调用此方法进行连接。
     * @param format
     * @throws Exception
     */
    private void connect(DataStoreFactorySpi format) throws Exception {
        JDataStoreWizard wizard = new JDataStoreWizard(format);
        int result = wizard.showModalDialog();
        if (result == JWizard.FINISH) {
            Map<String, Object> connectionParameters = wizard.getConnectionParameters();
            dataStore = DataStoreFinder.getDataStore(connectionParameters);
            if (dataStore == null) {
                JOptionPane.showMessageDialog(null, "Could not connect - check parameters");
            }
            updateUI();
        }
    }

    /**
     * 帮助方法来更新组合框用于选择一个功能类型:
     * @throws Exception
     */
    private void updateUI() throws Exception {
        ComboBoxModel<String> cbm = new DefaultComboBoxModel<>(dataStore.getTypeNames());
        featureTypeCBox.setModel(cbm);

        table.setModel(new DefaultTableModel(5, 5));
    }

    /**
     * 过滤器类似于SQL语句的where子句;定义每个特性需要满足的条件，以便选择
     *
     * 以下是我们的策略，显示选定的功能:
     *  获取用户选择的特性类型名称，并从数据存储中检索相应的FeatureSource。
     *  获取在文本字段中输入的查询条件，并使用CQL类创建筛选器对象。
     *  将筛选器传递给getFeatures方法，该方法将与查询匹配的特性作为特征回忆返回。
     *  为对话框的JTable创建一个FeatureCollectionTableModel。这个GeoTools类获取一个featurecall并检索每个特性的特性属性名称和数据。
     *
     *  考虑到这个策略，这里是实现
     *
     *  使用featureSource.getFeatures(filter)获取特性数据
     * @throws Exception
     */
    private void filterFeatures() throws Exception {
        String typeName = (String) featureTypeCBox.getSelectedItem();
        SimpleFeatureSource source = dataStore.getFeatureSource(typeName);

        Filter filter = CQL.toFilter(text.getText());
        SimpleFeatureCollection features = source.getFeatures(filter);
        FeatureCollectionTableModel model = new FeatureCollectionTableModel(features);
        table.setModel(model);
    }

    /**
     * featurecall的行为类似于一个预定义的查询或结果集，并且不将数据加载到内存中。
     * 您可以使用可用的方法询问关于featurecall的问题。
     * @throws Exception
     */
    private void countFeatures() throws Exception {
        String typeName = (String) featureTypeCBox.getSelectedItem();
        SimpleFeatureSource source = dataStore.getFeatureSource(typeName);

        Filter filter = CQL.toFilter(text.getText());
        SimpleFeatureCollection features = source.getFeatures(filter);

        int count = features.size();
        JOptionPane.showMessageDialog(text, "Number of selected features:" + count);
    }

    /**
     * 通过使用查询数据结构，您可以对请求进行更大的控制，允许您只选择所需的属性;控制返回多少特性;并要求一些特定的处理步骤，如重新投影。
     *
     * 下面是一个只选择geometry属性并将其显示在表中的示例
     * @throws Exception
     */
    private void queryFeatures() throws Exception {
        String typeName = (String) featureTypeCBox.getSelectedItem();
        SimpleFeatureSource source = dataStore.getFeatureSource(typeName);

        FeatureType schema = source.getSchema();
        String name = schema.getGeometryDescriptor().getLocalName();

        Filter filter = CQL.toFilter(text.getText());

        Query query = new Query(typeName, filter, new String[] {name});

        SimpleFeatureCollection features = source.getFeatures(query);

        FeatureCollectionTableModel model = new FeatureCollectionTableModel(features);
        table.setModel(model);
    }
}
